package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.hmdp.dto.Result;
import com.hmdp.entity.RedisData;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.CacheClient;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.SystemConstants;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.domain.geo.GeoReference;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.SHOP_GEO_KEY;


/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {




    @Resource
    private CacheClient cacheClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryByid(Long id) throws JsonProcessingException {
        //1.缓存穿透问题及解决
//        Shop shop = queryWithPassThrough(id);
        //2.缓存击穿问题及解决方案
//        Shop shop = queryWithMutex(id);

//        Shop shop = queryWithLogicalExpired(id);
        Shop shop = cacheClient.queryWithLogicalExpired(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);
        if (shop == null) {
            saveShop2Redis(id,2L);
            return Result.fail("店铺不存在");
        }
        return Result.ok(shop);

    }

//    private Shop queryWithLogicalExpired(Long id) {
//        //1.从redis中查询商户缓存
//        String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
//        //2.判断缓存是否命中 未命中返回空
//        if (StrUtil.isBlank(shopJson)) {
//            return null;
//        }
//        //3.命中
//        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
//        JSONObject data = (JSONObject) redisData.getData();
//        Shop shop = BeanUtil.toBean(data, Shop.class);
//        //4.判断缓存商户信息是否过期  如果为true则表示该商户信息已过期
//        if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
//           return shop;
//        }
//        //5.尝试获取互斥锁 判断是否拿到互斥锁
//        try {
//            boolean tryLock = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
//            if (tryLock){
//                String dataStr = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
//                RedisData dataJson = JSONUtil.toBean(dataStr, RedisData.class);
//                if (dataJson.getExpireTime().isAfter(LocalDateTime.now())) {
//                    return JSONUtil.toBean(JSONUtil.toJsonStr(dataJson.getData()),Shop.class);
//                }
//                CACHE_REBUILD_EXECUTOR.submit(()->{
//                   saveShop2Redis(id,30L);
//                });
//            }
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        } finally {
//            //释放锁
//            unLock(RedisConstants.LOCK_SHOP_KEY + id);
//        }
//        return shop;
//    }


//    private Shop queryWithMutex(Long id) {
//        //1.从redis中查询商户缓存
//        String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
//        //2.判断是否为空，不为空为命中转换为对应的商户对象返回
//        //三种情况 真实命中缓存在redis中商户信息  命中缓存中为“”的商户数据 没有命中则为null去查询数据库
//        if (StrUtil.isNotBlank(shopJson)) {
//            return JSONUtil.toBean(shopJson, Shop.class);
//        }
//        //2.1判断是否真正命中缓存
//        if (shopJson != null){
//            return null;
//        }
//        String LockKey = "lock:shop:" + id;
//        Shop byIdShop = null;
//        try {
//            boolean isLock = tryLock(LockKey);
//            if (!isLock) {
//                Thread.sleep(50);
//                return queryWithMutex(id);
//            }
//            //获取锁成功再次检测redis缓存释放存在,做双重检查
//            String jsonShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
//            if (StrUtil.isNotBlank(jsonShop)) {
//                return JSONUtil.toBean(jsonShop, Shop.class);
//            }
//            //3.为空则代表未命中 根据商户id查询数据库
//            byIdShop = getById(id);
//            //3.1 判断从数据库中查询出来的商户是否为空 为空则写入redis
//            if (byIdShop == null) {
//                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
//                return null;
//            }
//            //4.存在写入redis并返回
//            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(byIdShop),RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
//
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        } finally {
//            //释放锁
//            unLock(LockKey);
//        }
//        return byIdShop;
//    }


//    //保存商铺信息到redis
    public void saveShop2Redis(Long id,Long expiredTime) {
        Shop shop = getById(id);
        if (shop == null) {
            return;
        }
        RedisData redisData = RedisData.builder()
                .data(shop)
                .expireTime(LocalDateTime.now().plusSeconds(expiredTime))
                .build();
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }

//    private Shop queryWithPassThrough(Long id) {
//        //1.从redis中查询商户缓存
//        String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
//        //2.判断是否为空，不为空为命中转换为对应的商户对象返回
//        //三种情况 真实命中缓存在redis中商户信息  命中缓存中为“”的商户数据 没有命中则为null去查询数据库
//        if (StrUtil.isNotBlank(shopJson)) {
//             return JSONUtil.toBean(shopJson, Shop.class);
//        }
//        //2.1判断是否真正命中缓存
//        if (shopJson != null){
//            return null;
//        }
//        //3.为空则代表未命中 根据商户id查询数据库
//        Shop byIdShop = getById(id);
//        //3.1 判断从数据库中查询出来的商户是否为空 为空则写入redis
//        if (byIdShop == null) {
//            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
//            return null;
//        }
//        //4.存在写入redis并返回
//        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(byIdShop),RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
//        return byIdShop;
//    }

        @Override
        public Result update (Shop shop){
            //1.判断商户id是否为空值
            if (shop.getId() == null) {
                Result.fail("商户id不能为空！！！");
            }
            //2.修改对于的数据库商户信息
            updateById(shop);
            //3.删除对于缓存
            stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + shop.getId());
            return Result.ok();
        }

    @Override
    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        //1. 判断经纬度是否为null 如果为null则代表不需要根据距离进行查询
        if (x == null || y == null) {
            Page<Shop> shopPage = query().eq("type_id", typeId)
                    .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
            return Result.ok(shopPage);
        }
        //2. 如果执行到这里说明 需要根据经纬度进行距离查询
        // 2.1 计算起始条件
        int form = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
        // 2.2 计算结束条件
        int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
        // 拼接key去redis中进行查找商户id
        String key = SHOP_GEO_KEY + typeId;
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key,
                GeoReference.fromCoordinate(x, y),
                new Distance(5000),
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoSearchArgs().includeDistance().limit(end));
        // 如果为空直接返回一个空集合
        if (results == null){
            return Result.ok(Collections.EMPTY_LIST);
        }
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
        // 可能会有查找到的数据小于起始条件被跳过的情况 如果集合长度小于from直接返回空集合
        if (form >= list.size()) {
            return Result.ok(Collections.EMPTY_LIST);
        }
        // 遍历集合 获取信息 创建一个存储id的集合
        List<Long> ids = new ArrayList<>(list.size());
        Map<String,Distance> distanceMap = new HashMap<>(list.size());
        list.stream().skip(form).forEach(result -> {
            String id = result.getContent().getName();
            ids.add(Long.valueOf(id));
            // 距离信息和每个商户的id要一一对应使用map存储
            Distance distance = result.getDistance();
            distanceMap.put(id, distance);
        });
        // 根据id去数据库进行查询
        String strIds = StrUtil.join(",", ids);
        List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + strIds + ")").list();
        // 遍历商户集合赋值 距离信息
        for (Shop shop : shops) {
            shop.setDistance(distanceMap.get(shop.getId()).getValue());
        }
        return Result.ok(shops);
    }

//    @Override
//    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
//        //1. 判断经纬度是否为null 如果为null则不需要根据距离查询 从数据库分页查询商户信息即可
//        if (x == null || y == null) {
//            Page<Shop> shopPage = query().eq("type_id", typeId)
//                    .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
//            return Result.ok(shopPage);
//        }
//        //2. 如果不为空 需要距离查询
//        // 2.1 计算分页条件
//        int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
//        int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
//        // 2.2 拼接key
//        String key = SHOP_GEO_KEY + typeId;
//        // 2.3 从redis查找相关距离内的商户信息
//        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(
//                key,
//                GeoReference.fromCoordinate(x, y),
//                new Distance(5000),
//                RedisGeoCommands.GeoRadiusCommandArgs.newGeoSearchArgs().includeDistance().limit(end));
//        // 3  如果为空 则返回一个空集合
//        if (results == null){
//            return Result.ok(Collections.EMPTY_LIST);
//        }
//        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
//        if (from >= list.size()) {
//            return Result.ok(Collections.EMPTY_LIST);
//        }
//        // 4. 解析id
//        List<Long> ids = new ArrayList<>(list.size());
//        Map<String,Distance> distanceMap = new HashMap<>(list.size());
//        list.stream().skip(from).forEach(result -> {
//            String shopId = result.getContent().getName();
//            ids.add(Long.parseLong(shopId));
//
//            Distance distance = result.getDistance();
//            distanceMap.put(shopId, distance);
//        });
//        // 根据从redis中收集好的id去数据库中查找商户信息
//        String StrIds = StrUtil.join(",", ids);
//        List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + StrIds + ")").list();
//        // 遍历赋值距离信息
//        for (Shop shop : shops) {
//            shop.setDistance(distanceMap.get(shop.getId()).getValue());
//        }
//        return Result.ok(shops);
//    }
}
